package test;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class Stream implements Runnable {
		private int port = 4444;
		private int sampleDelay = 1000;
		private String ODRoot = "OD/";
		private String OD_path = ODRoot + "OD.xml";
		private Double[] values = new Double[2048];
		private HashMap<String,HashMap<Integer,String>> allDataTypes = new HashMap<String,HashMap<Integer,String>>();
		public enum Datatypes
		{
			q1616, sq1616,uint32_t,int32_t,bool,eplreg, statusreg, errorreg, 
			nodesonlinemasterdef, gpsdegdef, gearregdef, defaulttype;
		    
		    public static Datatypes toDatatype(String datatype)
		    {
		        try {
		        	if (datatype.length() > 8 && datatype.substring(0, 9).equalsIgnoreCase("StatusReg")) {
		        		return statusreg;
		        	} else if (datatype.length() > 7 && datatype.substring(0, 8).equalsIgnoreCase("ErrorReg")) {
		        		return errorreg;
		        	} else {
		        		return valueOf(datatype.toLowerCase().replace(".", ""));
		        	}
		        } 
		        catch (Exception ex1) {
			           return defaulttype;
		        }
		    } 
		}
		
		public void run() {
			ServerSocket serverSocket;
			try {
			    serverSocket = new ServerSocket(port);
			    
			    // Start socket factory:
			    new Thread(new SocketFactory(serverSocket)).start();
			    System.out.println("Server is running");
			} catch (IOException e) {
			    
			}
		}
		
		private class SocketFactory implements Runnable {
			private Socket clientSocket;
			private ServerSocket serverSocket;
			
			public SocketFactory(ServerSocket serverSocket) {
				this.serverSocket = serverSocket;
			}
			
			public void run() {
				try {
					while (true) {
						clientSocket = serverSocket.accept();
						
						new Thread(new ServerThread(clientSocket)).start();
					}
				} catch (IOException e) {
				}
			}
			
		}
		
		private class ServerThread implements Runnable{
		    private Socket clientSocket;
		    private BufferedReader input;
		    private OutputStream output;
		    private boolean first = true;
		    
		    public ServerThread(Socket clientSocket) {
		        this.clientSocket = clientSocket;
		        
		        try {
					//input  = new BufferedReader(new InputStreamReader
					//		(this.clientSocket.getInputStream(),"ISO-8859-1"));
					output = this.clientSocket.getOutputStream();
				} catch (IOException e) {
					try {
						this.clientSocket.close();
					} catch (IOException e1) {}
				}
		    }

		    public void run() {
		    	try {
		    		System.out.println("Client connected: " + 
		    							clientSocket.getRemoteSocketAddress());
		    		byte[] startTag1 = "OD Data\n".getBytes();
        			byte[] startTag2 = "Data length: 2048\n".getBytes();
		    		while(true) {
		    			try {
		    				output.write(startTag1);
		    				output.write(startTag2);
		    				output.write(generateOD());
		    				Thread.sleep(sampleDelay);
		    			} catch (Exception e) {
		    				break;
		    			}
		    		}
		    		output.close();
		    		input.close();
		    	} catch (Exception e) {
		    	} finally {
		    		System.out.println("Client disconnected: " + clientSocket.getRemoteSocketAddress());
		    	}
		    }
		    
		    private byte[] generateOD() {
		    	byte[] OD = new byte[2048];
				
				try {
					File file = new File(OD_path);
					DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
					DocumentBuilder db = dbf.newDocumentBuilder();
					Document doc = db.parse(file);
					doc.getDocumentElement().normalize();
					NodeList nodeList = doc.getElementsByTagName("node");
					
					for (int i = 0; i < nodeList.getLength(); i++) {
						Node curNode = nodeList.item(i);
						    
						if (curNode.getNodeType() == Node.ELEMENT_NODE) {
							Element nodeElement = (Element) curNode;
							int addressOffset = Integer.parseInt(nodeElement.getElementsByTagName("BRAMOffset").item(0).getChildNodes().item(0).getNodeValue());
							NodeList parameters = nodeElement.getElementsByTagName("TPDO");
							
							for (int j = 0; j < parameters.getLength(); j++) {
								Element currentParameter = (Element) parameters.item(j);
								
								int address = Integer.parseInt(currentParameter.
										getAttributes().getNamedItem("nodeindex").
										getNodeValue()) + addressOffset;
								
								String dataTypePath = ((Element) currentParameter.getElementsByTagName("PDODataType").item(0)).getAttribute("path");
								int dataTypeId = Integer.parseInt(((Element) currentParameter.getElementsByTagName("PDODataType").item(0)).getAttribute("id"));
								String binValue = "00000000000000000000000000000000";
								String datatype = this.getRegisterDatatype(dataTypePath, dataTypeId);
								
								try {
									
									NodeList dataRanges = currentParameter.getElementsByTagName("PDOValueRange").item(0).getChildNodes();
									Double min = Double.parseDouble(((Element) dataRanges.item(0)).getChildNodes().item(0).getNodeValue());
									Double max = Double.parseDouble(((Element) dataRanges.item(1)).getChildNodes().item(0).getNodeValue());
									
									Double random;
									if (first) {
										random = Math.random() * (max-min) + min;
										values[address] = random;
									} else {
										Double change = (Math.random() - 0.5) * (max-min)*0.1;
										random = values[address] + change;
										if (random < min) {
											random = min;
										}
										
										if (random > max) {
											random = max;
										}
										values[address] = random;
									}
									
									switch (Datatypes.toDatatype(datatype)) {
										case q1616:
											random = random * Math.pow(2., 16.);
											Long q1616 = random.longValue();
											String q1616Value = Long.toBinaryString(q1616);
											binValue = binValue.substring(q1616Value.length()) + q1616Value;
											break;
										case sq1616:
											Double intValues = random.intValue()*Math.pow(2., 16.);
											Integer sq1616 = intValues.intValue();
											random = Math.abs((random-random.intValue())*Math.pow(2., 16.));
											sq1616 = sq1616 + random.intValue();
											String sq1616Value = Integer.toBinaryString(sq1616);
											binValue = binValue.substring(sq1616Value.length()) + sq1616Value;
											break;
										case uint32_t:
											Long uint = random.longValue();
											String uintValue = Long.toBinaryString(uint);
											binValue = binValue.substring(uintValue.length()) + uintValue;
											break;
										case int32_t:
											Integer sint = random.intValue();
											String intValue = Integer.toBinaryString(sint);
											binValue = binValue.substring(intValue.length()) + intValue;
											break;
										case gpsdegdef:
											intValues = random.intValue()*Math.pow(2., 24.);
											Integer gpsdeg = intValues.intValue();
											random = Math.abs((random-random.intValue())*Math.pow(10., 6.));
											gpsdeg = gpsdeg + random.intValue();
											String gpsdegValue = Integer.toBinaryString(gpsdeg);
											binValue = binValue.substring(gpsdegValue.length()) + gpsdegValue;
											break;
									}
								} catch (Exception e) {
									Double random = Math.random();
									switch (Datatypes.toDatatype(datatype)) {
										case bool:
											binValue = (random < 0.5) ? "00000000000000000000000000000001" : "00000000000000000000000000000000";
											break;
										case eplreg:
											binValue = "00000000000000000000001100000000";
											break;
										case statusreg:
											binValue = "00001111110001001000001111000001";
											break;
										case errorreg:
											binValue = "01100000000000101000001011000001";
											break;
										case gearregdef:
											binValue = "00000000000000000000000000001000";
											break;
										case nodesonlinemasterdef:
											binValue = "00000000000000000000000010111101";
											break;
										case defaulttype:
										default:
											break;
									}
								}
								
								//System.out.println(address + ", " + datatype + ": " + binValue);
								//String binValue = Integer.toBinaryString(value);
								//binValue = "00000000000000000000000000000000".substring(binValue.length()) + binValue;
								//binValue =   "00000000000000000000000000000000";
								//binValue = "00000000000010010000000000000011";
										  //11101101101100001110100111000000
										  //1110110110110000
										  //11101101000000000000000000000000
										  //11101100101100000000000000000000
								try {
									OD[address*4+0] = (byte) (Integer.parseInt(binValue.substring(binValue.length()-8, binValue.length()),2));
									OD[address*4+1] = (byte) (Integer.parseInt(binValue.substring(binValue.length()-16, binValue.length()-8),2));
									OD[address*4+2] = (byte) (Integer.parseInt(binValue.substring(binValue.length()-24, binValue.length()-16),2));
									OD[address*4+3] = (byte) (Integer.parseInt(binValue.substring(binValue.length()-32, binValue.length()-24),2));
								} catch (StringIndexOutOfBoundsException e) {}
							}
							
						}
					}
				} catch (Exception e) {
				    e.printStackTrace();
				}
				first = false;
				return OD;
		    }
		    
		    public String getRegisterDatatype(String path, int id) {
		    	try {
		    		if (allDataTypes.get(path) == null) {
						File file = new File(ODRoot + path);
						DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
						DocumentBuilder db;
						
							db = dbf.newDocumentBuilder();
						
						Document doc = db.parse(file);
						doc.getDocumentElement().normalize();
						NodeList nodeList = doc.getElementsByTagName("DataType");
						HashMap<Integer,String> dataTypes = new HashMap<Integer,String>();
						
						for (int i = 0; i < nodeList.getLength(); i++) {
							Node curNode = nodeList.item(i);
							
							if (curNode.getNodeType() == Node.ELEMENT_NODE) {
								Element nodeElement = (Element) curNode;
								
								dataTypes.put(Integer.parseInt(nodeElement.getAttribute("id")), nodeElement.getElementsByTagName("Name").item(0).getChildNodes().item(0).getNodeValue());
							}
						}
						allDataTypes.put(path, dataTypes);
					}
		    	} catch (ParserConfigurationException e) {
					e.printStackTrace();
				} catch (SAXException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				}
				return allDataTypes.get(path).get(id);
			}
		}
	}